// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: jing

#pragma once

#include <cxxopts.hpp>

#include "coin2/app/driver.pb.h"
#include "coin2/strategy/strategy.h"
#include "coin2/feed/fastfeed/writer.h"
#include "coin2/app/driver.h"
#include "coin2/base/config/app_config.h"
#include "coin2/exchange/base/executor/sync_executor.h"
#include "coin2/exchange/base/market/market_exchange_api.h"
#include "coin2/exchange/base/symbology/product_holder_manager.h"

using namespace coin2::base::config;
using namespace coin2::exchange::feed;
using namespace coin2::app;
using namespace coin2::exchange::base::symbology;
using coin2::feed::fastfeed::FastFeedWriter;
using coin2::exchange::base::feed::FeedUpdate;
using coin2::exchange::di::ConstantInjectionBuilder;
using ::fastfeed::proto::BookBuilderParameter;

using coin2::app::DriverConfig;

struct Channel {
  MarketExchangeApi mea;
  std::string symbol;
};

struct FastFeedBuilder {
  explicit FastFeedBuilder(int64_t ts) : last_snapshot_ts(ts) {}
  int64_t last_snapshot_ts = std::numeric_limits<int64_t>::quiet_NaN();
  std::optional<bool> precision_price_ascending = std::nullopt;
  std::optional<bool> precision_qty_ascending = std::nullopt;
};

struct PrecisionMultiplier {
  int price_precision;
  int qty_precision;
  int64_t price_multiplier;
  int64_t qty_multiplier;
};

void SetupOptions(cxxopts::Options *opt);

void ParseOptionsIntoConfig(const cxxopts::ParseResult& res, DriverConfig* app);

void LogAppConfig(const cxxopts::ParseResult& res, const DriverConfig& app);


// create a fastfeed cache
// snapshot every 5 mins
// user is recommended to start 10 mins earlier
class FastFeedSubConverter : public coin2::strategy::IStrategy {
 public:
  explicit FastFeedSubConverter(
      const std::string& recipe,
      const std::string& out_dir,
      int multiplier = 1'000'000'000,
      const std::string& pi_dir = {});

  virtual void Init(::coin2::app::Driver* driver);
  virtual void Init(const ::coin2::app::DriverConfig* driver_config);

  // When each product's trade or book event appears for the first time
  // we write BookBuilderParameter to announce the precision
  static void onNewProduct(
      FastFeedWriter* writer,
      const std::string& symbol,
      int64_t ts,
      int price_precision,
      int qty_precision,
      int default_precision);

  FastFeedBuilder& GetFastFeedBuilder(
      const Channel& ch,
      FastFeedWriter* writer,
      const FeedUpdate& upd);

  const PrecisionMultiplier& GetPrecisionMultiplier(
      const Channel& ch,
      const FeedUpdate& upd);

  void onTradeFeed(const FeedUpdate& upd) override;
  void onBookFeed(const FeedUpdate& upd) override;
  void onLiquidationFeed(const FeedUpdate& upd) override;
  void onOpenInterestFeed(const FeedUpdate& upd) override;
  void onIndexFeed(const FeedUpdate& upd) override;
  void onMarkPriceFeed(const FeedUpdate& upd) override;
  void onFundingRateFeed(const FeedUpdate& upd) override;
  void onIvGreeksFeed(const FeedUpdate& upd) override;
  void onNavFeed(const FeedUpdate& upd) override;
  void onKlineFeed(const FeedUpdate& upd) override;
  void onWalletFeed(const FeedUpdate& upd) override;
  void onMarketAnalysisFeed(const FeedUpdate& upd) override;
  void onTopLongShortAccountRatioFeed(const FeedUpdate& upd) override;
  void onTopLongShortPositionRatioFeed(const FeedUpdate& upd) override;
  void onGlobalLongShortAccountRatioFeed(const FeedUpdate& upd) override;
  void onTakerBuySellRatioFeed(const FeedUpdate& upd) override;
  void onOpenInterestHistFeed(const FeedUpdate& upd) override;

  static const int64_t SNAPSHOT_PERIOD = 10 * 60 * 1'000'000'000LL;

 protected:
  void ConvertBookProto(
      const FeedUpdate& upd,
      std::optional<int64_t>& price_multiplier_new,
      std::optional<int64_t>& qty_multiplier_new);
  void ConvertTradeProto(
      const FeedUpdate& upd,
      std::optional<int64_t>& price_multiplier_new,
      std::optional<int64_t>& qty_multiplier_new);
  void CheckAndUpdatePricePrecision(
      const Channel& ch,
      std::optional<bool>& price_ascending,
      int64_t price_multiplier_new);
  void CheckAndUpdateQtyPrecision(
      const Channel& ch,
      std::optional<bool>& qty_ascending,
      int64_t qty_multiplier_new);
  void WritePrecisionMultiplier(
      const FeedUpdate& upd,
      const std::optional<int64_t>& price_multiplier_new,
      const std::optional<int64_t>& qty_multiplier_new);
  std::string recipe_;
  bool is_trade_move2bp_;
  std::experimental::filesystem::path out_dir_;
  int default_multiplier_;
  std::string pi_dir_;
  int64_t start_from_;
  bool publish_bbo_book_only_;
  bool publish_bbo_price_change_;
  int publish_book_level_;
  std::unique_ptr<FastFeedWriter> writer_;
  mutable fastfeed::proto::Feed proto_;
  std::unique_ptr<coin2::exchange::base::symbology::ProductHolderManager> product_holder_manager_;

 private:
  std::map<Channel, FastFeedBuilder> builders_;
  std::map<Channel, PrecisionMultiplier> precision_multiplier_cache_;
  std::map<std::pair<std::string, fastfeed::proto::KlineInterval>,
           int64_t> klines_last_open_timestamp_;
  std::map<std::pair<std::string, fastfeed::proto::TradingDataInterval>,
           int64_t> top_account_last_timestamp_;
  std::map<std::pair<std::string, fastfeed::proto::TradingDataInterval>,
           int64_t> top_position_last_timestamp_;
  std::map<std::pair<std::string, fastfeed::proto::TradingDataInterval>,
           int64_t> global_account_last_timestamp_;
  std::map<std::pair<std::string, fastfeed::proto::TradingDataInterval>,
           int64_t> taker_volume_last_timestamp_;
  std::map<std::string, std::pair<
        std::optional<double>, std::optional<double>>> last_bbo_prices_;
  std::map<std::pair<std::string, fastfeed::proto::TradingDataInterval>,
           int64_t> open_interest_hist_last_timestamp_;
};

class FastFeedConverter : public coin2::strategy::IStrategy {
 public:
  explicit FastFeedConverter(
      const std::string& out_dir,
      int multiplier = 1'000'000'000,
      const std::string& pi_dir = {});
  virtual void Init(::coin2::app::Driver* driver);
  virtual void Init(const ::coin2::app::DriverConfig* driver_config);
  void onTradeFeed(const FeedUpdate& upd) override;
  void onBookFeed(const FeedUpdate& upd) override;
  void onLiquidationFeed(const FeedUpdate& upd) override;
  void onOpenInterestFeed(const FeedUpdate& upd) override;
  void onIndexFeed(const FeedUpdate& upd) override;
  void onMarkPriceFeed(const FeedUpdate& upd) override;
  void onFundingRateFeed(const FeedUpdate& upd) override;
  void onIvGreeksFeed(const FeedUpdate& upd) override;
  void onNavFeed(const FeedUpdate& upd) override;
  void onKlineFeed(const FeedUpdate& upd) override;
  void onWalletFeed(const FeedUpdate& upd) override;
  void onMarketAnalysisFeed(const FeedUpdate& upd) override;
  void onTopLongShortAccountRatioFeed(const FeedUpdate& upd) override;
  void onTopLongShortPositionRatioFeed(const FeedUpdate& upd) override;
  void onGlobalLongShortAccountRatioFeed(const FeedUpdate& upd) override;
  void onTakerBuySellRatioFeed(const FeedUpdate& upd) override;
  void onOpenInterestHistFeed(const FeedUpdate& upd) override;

 protected:
  std::experimental::filesystem::path out_dir_;
  std::string pi_dir_;
  std::unordered_map<std::string, std::unique_ptr<FastFeedSubConverter>> sub_converters_;

 private:
  int default_multiplier_;
};
